package efg;

import static interfacesFerrovia.DefsGlobais.TipoAtributo.ID;
import static interfacesFerrovia.DefsGlobais.TipoAtributo.TIPO;
import interfacesFerrovia.ControleFerrovia;
import interfacesFerrovia.DefsGlobais.TipoAtributo;
import interfacesFerrovia.DefsGlobais.TipoElemento;

import java.util.AbstractQueue;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;

import efg.tasks.SchedulableTask;

public class Railway {
	
	private ControleFerrovia railwayControl;
	private Map<String, RailwayElement> railwayElements;
	private Map<String, Train> trains;
	private Map<String, Track> tracks;
	private Map<String, Switch> switches;
	
	private AbstractQueue<SchedulableTask> scheduledTasks;
	private Set<SelfCheckableTask> checkableTasks;
	
	public Railway(ControleFerrovia railwayControl) {
		assert (railwayControl != null);
		assert (railwayControl.consultaElementos().length > 0);
		
		this.railwayControl = railwayControl;
		this.scheduledTasks = new PriorityBlockingQueue<SchedulableTask>();
		checkableTasks = new HashSet<SelfCheckableTask>();
		initialize();
		
		assert(! railwayElements.isEmpty());
	}
	
	private void initialize() {
		railwayElements = new HashMap<String, RailwayElement>();
		trains = new HashMap<String, Train>();
		tracks = new HashMap<String, Track>();
		switches = new HashMap<String, Switch>();
		
		String[] ids = this.railwayControl.consultaElementos();
		for (String id : ids) {
			Map<TipoAtributo, Object> props = railwayControl
					.consultaAtributosFixos(id);
			assert (props.get(ID).equals(id));
			TipoElemento tipo = (TipoElemento) props.get(TIPO);
			switch (tipo) {
			case CHAVE: {
				Switch s = new Switch(id, this);
				railwayElements.put(id, s);
				switches.put(id, s);
				break;
			}
			case COMPOSICAO: {
				Train t = new Train(id, this);
				railwayElements.put(id, t);
				trains.put(id, t);
				break;
			}
			case SENSOR: {
				assert(false);
				break;
			}
			case TRECHO: {
				Track t = new Track(id, this);
				railwayElements.put(id, t);
				tracks.put(id, t);
			}
			}
		}
	}

	public RailwayElement getElementById(String elId) {
		assert (railwayElements != null);
		assert (railwayElements.containsKey(elId));
		RailwayElement el = railwayElements.get(elId);
		assert (el != null);
		assert (el.getId().equals(elId));
		return el;
	}
	
	public void dumpState() {
		String[] ids = railwayControl.consultaElementos();
		for (String id : ids) {
			Map<TipoAtributo, Object> props = railwayControl.consultaEstado(id);
			System.out.println(props);
		}
	}
	
	public Collection<Train> getTrains() {
		assert(trains != null);
		assert(! trains.isEmpty());
		return trains.values();
	}
	
	public Collection<Track> getTracks() {
		assert(tracks != null);
		assert(! tracks.isEmpty());
		return tracks.values();
	}
	
	public Collection<Switch> getSwitches() {
		assert(switches != null);
		assert(! switches.isEmpty());
		return switches.values();
	}

	public Object getAttribute(String id, TipoAtributo attribute) {
		assert(id != null);
		assert(attribute != null);
		return railwayControl.consultaAtributo(id, attribute);
	}

	public void registerTask(SchedulableTask task) {
		assert(this.scheduledTasks != null && task != null);
		this.scheduledTasks.add(task);
		System.out.println("Registrou tarefa");
	}
	
	public void registerTask(SelfCheckableTask task) {
		assert(this.checkableTasks != null && task != null);
		this.checkableTasks.add(task);
	}
	
	public void executeTasks(Integer currentTime) {
		assert(this.scheduledTasks != null);
		
		//Scheduled tasks
		while ((!scheduledTasks.isEmpty()) && scheduledTasks.element().getScheduledTime() <= currentTime) {
			SchedulableTask next = scheduledTasks.remove();
			next.execute(railwayControl);
			System.out.println("Executou Tarefa Agendada. Tempo="+currentTime);
		}
		
		// self checking tasks
		for(Iterator<SelfCheckableTask> it = checkableTasks.iterator(); it.hasNext();) {
			SelfCheckableTask task = it.next();
			if(task.canExecute()) {
				System.out.println("Executou Tarefa auto-checavel. Tempo="+currentTime);
				it.remove();
				task.execute(currentTime);
			}
		}
	}

	public ControleFerrovia getRailwayControl() {
		return railwayControl;
	}

	public void stopAllTrains() {
		Collection<Train> trains = getTrains();
		for (Train train : trains) {
			train.speedDown(0f, true); // para todo mundo
		}		
	}

	public void stuntAllTrains() {
		for (Train train : getTrains()) {
			train.setSpeed(0.001f, true); // para todo mundo
		}
		scheduledTasks.clear();
		checkableTasks.clear();
		System.err.println("Colisão frontal inevitavel!!! Abortando tudo!!");
	}

}
